/*	$Id: timer.c 5 2008-07-29 02:22:57Z phrakt $	*/
/*
 * Copyright (c) 2003 Jean-Francois Brousseau <jfb@openbsd.org>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/types.h>
#include <sys/time.h>

#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <pthread.h>

#include "timer.h"
#include "queue.h"
#include "pforge.h"


static void*   pf_timer_watcher (void *);


static LIST_HEAD(, pf_timer) pf_timer_list;
static pthread_mutex_t      pf_timer_mtx = PTHREAD_MUTEX_INITIALIZER;


static pthread_t            pf_timer_id;
static pthread_cond_t       pf_timer_cond = PTHREAD_COND_INITIALIZER;

/* default timeout for empty list */
static struct timespec      pf_timer_timeout;


/*
 * pf_timer_init()
 *
 * Initialize the timer mechanism.
 * Returns 0 on success, or -1 on failure.
 */

int
pf_timer_init(void)
{
	int ret;
	struct timeval tv;

	(void)gettimeofday(&tv, NULL);
	pf_timer_timeout.tv_sec = tv.tv_sec + 1;
	pf_timer_timeout.tv_nsec = tv.tv_usec * 1000;

	ret = pthread_cond_init(&pf_timer_cond, NULL);
	if (ret != 0) {
		return (-1);
	}

	ret = pthread_mutex_init(&pf_timer_mtx, NULL);
	if (ret != 0) {
		return (-1);
	}

	ret = pthread_create(&pf_timer_id, NULL, pf_timer_watcher, NULL);
	if (ret != 0) {
		return (-1);
	}

	return (0);
}


/*
 * pf_timer_cleanup()
 */

void
pf_timer_cleanup(void)
{

}


/*
 * pf_timer_add()
 *
 * Add a timer to be triggered every <usec> microseconds.  On every trigger,
 * the handler <hdlr> is called with <arg> as argument.  The <flags> argument
 * is used to alter the behaviour of the timer.
 * Returns a timer handle on success, or PFTIMER_FAILED on failure.
 */

pf_timer_t
pf_timer_add(int usec, int flags, pf_timer_hdlr hdlr, void *arg)
{
	int ret;
	struct pf_timer *tm;
	struct timeval tv;

	(void)gettimeofday(&tv, NULL);

	tm = (struct pf_timer *)pf_malloc(sizeof(*tm));
	if (tm == NULL) {
		return (PFTIMER_FAILED);
	}
	memset(tm, 0, sizeof(*tm));
	tm->tm_usec = usec;
	tm->tm_flags = flags;
	tm->tm_hdlr = hdlr;
	tm->tm_arg = arg;

	/* calculate when the next timer will be triggered */
	tv.tv_usec += usec;
	if (tv.tv_usec >= 1000000) {
		tv.tv_usec -= 1000000;
		tv.tv_sec++;
	}

	tm->tm_next.tv_sec = tv.tv_sec;
	tm->tm_next.tv_nsec = tv.tv_usec * 1000;

	ret = pthread_mutex_lock(&pf_timer_mtx);
	if (ret != 0) {
		pf_free(tm);
		return (PFTIMER_FAILED);
	}
	LIST_INSERT_HEAD(&pf_timer_list, tm, tm_list);
	(void)pthread_mutex_unlock(&pf_timer_mtx);

	/* signal the addition of a new timer */
	(void)pthread_cond_signal(&pf_timer_cond);

	return (tm);
}


/*
 * pf_timer_del()
 *
 * Delete a timer from the list of delivered timers.
 */

int
pf_timer_del(pf_timer_t timer)
{
	int ret;

	ret = pthread_mutex_lock(&pf_timer_mtx);
	if (ret != 0) {
		return (-1);
	}
	LIST_REMOVE(timer, tm_list);
	(void)pthread_mutex_unlock(&pf_timer_mtx);
	(void)pthread_cond_signal(&pf_timer_cond);

	return (0);
}


/*
 * pf_timer_watcher()
 *
 * Watcher thread for the timer list.  We recheck the list on every trigger to
 * update the closest timer to be triggered, then sleep until the time has
 * arrived.
 */

static void*
pf_timer_watcher(void *arg)
{
	int ret;
	struct timespec *ts;
	struct pf_timer *pftmp, *closest;

	/* initialize dummy timer for when list is empty */

	for (;;) {
		closest = NULL;
		ts = &pf_timer_timeout;

		ret = pthread_mutex_lock(&pf_timer_mtx);
		if (ret != 0) {
		}
		LIST_FOREACH(pftmp, &pf_timer_list, tm_list) {
			if ((closest == NULL) ||
			    (timespeccmp(&(pftmp->tm_next), ts, <))) {
				closest = pftmp;
				ts = &(closest->tm_next);
			}
		}

		ret = pthread_cond_timedwait(&pf_timer_cond, &pf_timer_mtx, ts);
		if ((ret == ETIMEDOUT) && (closest != NULL)) {
			closest->tm_hdlr(closest->tm_arg);

			closest->tm_next.tv_nsec += closest->tm_usec * 1000;
			if (closest->tm_next.tv_nsec >= 1000000000) {
				closest->tm_next.tv_sec++;
				closest->tm_next.tv_nsec -= 1000000000;
			}
		}

	}

	return (NULL);
}
